perm filename METHCO.L[FTL,LSP] blob sn#826369 filedate 1986-10-21 generic text, type T, neo UTF8
;;; -*- Mode:LISP; Package:(PCL LISP 1000); Base:10; Syntax:Common-lisp -*-
;;;
;;; *************************************************************************
;;; Copyright (c) 1985 Xerox Corporation.  All rights reserved.
;;;
;;; Use and copying of this software and preparation of derivative works
;;; based upon this software are permitted.  Any distribution of this
;;; software or derivative works must comply with all applicable United
;;; States export control laws.
;;; 
;;; This software is made available AS IS, and Xerox Corporation makes no
;;; warranty about the software, its performance or its conformity to any
;;; specification.
;;; 
;;; Any person obtaining a copy of this software is requested to send their
;;; name and post office or electronic mail address to:
;;;   CommonLoops Coordinator
;;;   Xerox Artifical Intelligence Systems
;;;   2400 Hanover St.
;;;   Palo Alto, CA 94303
;;; (or send Arpanet mail to CommonLoops-Coordinator.pa@Xerox.arpa)
;;;
;;; Suggestions, comments and requests for improvements are also welcome.
;;; *************************************************************************
;;;
;;; User-defined method combination.  A first try.
;;;
;;; For compatibility with New Flavors, the following functions macros and
;;; variables have the same meaning.
;;;   define-simple-method-combination
;;;   define-method-combination
;;;   call-component-method
;;;   call-component-methods
;;;   *combined-method-arguments*
;;;   *combined-method-apply*
;;;
;;; In define-method-combination the arguments have the following meanings:
;;;
;;;   name             the name of this method combination type (symbol)
;;;   parameters       like a defmacro lambda list, it is matched with
;;;                    the value specified by the :method-combination
;;;                    option to make-specializable
;;;   method-patterns  a list of method-patterns specifications that are
;;;                    used to select some subset of the methods defined
;;;                    on the discriminator.  Each method pattern specifies
;;;                    a variable which is bound to a list of the methods
;;;                    it selects.
;;;   body             forms evaluated with the variables specified by
;;;                    the method patterns bound to produce the body of
;;;                    the combined method.  (see call-component-methods).
;;;
;;;  Body can be preceded by any number of options which take the form:
;;;    (<option-name> . <option-args>)
;;;
;;;  Currently, the defined options are:
;;;
;;;   :causes-combination-predicate
;;;       The only argument, should be a function of one argument.  It
;;;       will be called on a method (of the discriminator) and should
;;;       return T if that method causes the discriminator to combine
;;;       its methods.
;;;
;;; A method-patterns looks like:
;;;                    
;;;   (<var> <printer> <filter> <order> <pattern-1> <pattern-2> ..)
;;;
;;;      <var>      is the variable to which the selected methods
;;;                 are bound
;;;      <printer>  is ignored
;;;      <filter>   one of :every, :first, :last or :remove-duplicates
;;;      <order>    :most-specific-first or :most-specific-last
;;;
;;;      Methods matching any of the patterns are selected.  The patterns
;;;      are matched against the method-combination-options of the method
;;;      as specified in the defmeth.
;;; 

(in-package 'pcl)

;;;
;;; The method combination type of a particular method combination is stored
;;; as a symbol (the name of the method-combination) in the discriminator (in
;;; the method-combination-type slot).  Information about that particular
;;; method-combination-type is stored on the property list of the type symbol
;;; 
(defun get-method-combination-info (type &optional no-error-p)
  (or (get type 'method-combination)
      (if no-error-p () (error "No method combination named ~S." type))))

(defun set-method-combination-info (type combiner predicate)
  (setf (get type 'method-combination) (list type combiner predicate)))

(defmeth method-combiner ((discriminator method-combination-mixin))
  (cadr (get-method-combination-info
          (method-combination-type discriminator))))

(defmeth method-causes-combination-predicate
         ((discriminator method-combination-mixin))
  (caddr (get-method-combination-info
           (method-combination-type discriminator))))




  ;;   
;;;;;; COMBINED-METHOD class
  ;;   

(ndefstruct (combined-method (:class class)
                             (:include (method)))
  (deactivated-methods ()))

(defmeth automatically-defined-p ((m combined-method)) (ignore m) t)

(defmeth method-options ((m combined-method)) (ignore m) '(:combined))
                                                
(defmeth method-causes-combination-p ((m combined-method)) (ignore m) nil)

(defmacro define-simple-method-combination (name operator
                                            &optional single-arg-is-value
                                                      (pretty-name
                                                        (string-downcase
                                                          name)))
  `(define-method-combination ,name
                              (&optional (order :most-specific-first))
             ((methods ,pretty-name :every order () (,name) :default))
     `(call-component-methods ,methods
                              :operator ,',operator
                              :single-arg-is-value ,',single-arg-is-value)))

(defmacro define-method-combination (name parameters method-patterns
                                     &body body)
  (check-type parameters list)
  (check-type method-patterns (and list (not null)))
  (make-method-combination name parameters method-patterns body))


(defvar *combined-method-arguments*)
(defvar *combined-method-apply*)
(defvar *combined-method-template*)

;;;
;;; Generate a form that calls a single method.
;;; With no keyword arguments, uses the value of *combined-methods-arguments*
;;; as the arguments to the call;
;;; With :ARGLIST, uses that instead;
;;; With :ARGLIST and :APPLY T, uses APPLY instead of FUNCALL
;;; With just :APPLY, it is the single argument to apply to.
;;;
;;; When called with *combined-method-template* bound, generates calls to
;;; the value of variables gotten from *combined-method-template* instead
;;; of to the actual methods themselves.  This is used to build templates
;;; for combined methods.
;;;
(defmacro call-component-method
          (method &key (apply nil apply-p)
                       (arglist 
                         (if apply-p
                             (prog1 (list apply) (setq apply t))
                             (prog1 *combined-method-arguments*
                                    (setq apply *combined-method-apply*)))))
  (call-component-method-internal method apply arglist))

(defmacro call-component-methods (methods &key (operator 'progn)
                                               (single-arg-is-value nil))
  (call-component-methods-internal methods operator single-arg-is-value))

(defmeth call-component-method-internal
         (method &optional (apply *combined-method-apply*)
                           (arglist *combined-method-arguments*))
  (when method
    `(,(if apply 'apply 'funcall)
      ,(if (boundp '*combined-method-template*)
	   (let ((gensym (cdr (assq method *combined-method-template*))))
	     (if gensym
		 `(the function ,gensym)
		 (error "*combined-method-template* out of sync??")))
	   `',(method-function method))
      ,@arglist)))
  
(defmeth call-component-methods-internal (methods
					  operator single-arg-is-value)
  (when methods
    (if (and single-arg-is-value (null (cdr methods)))
	(call-component-method-internal (car methods))
	`(,operator
	  ,@(iterate ((method in methods))
	      (collect (call-component-method-internal method)))))))

(defmeth call-component-method-equal (discriminator call-1 call-2)
  ;; If the options are the same (the part that the macros control the
  ;; processing of); and the individual calls are the same the part the
  ;; methods themselves control the processing of.
  (and (equal (cddr call-1) (cddr call-2))
       (if (eq (car call-1) 'call-component-method)
	   (cond ((null (cadr call-1)) (null (cadr call-2)))
		 ((null (cadr call-2)) (null (cadr call-1)))
		 (t
		  (call-component-method-equal-internal
		    discriminator (cadr call-1) (cadr call-2))))
           (iterate ((meth-1 on (cadr call-1))
                     (meth-2 on (cadr call-2)))
	     (when (or (and (cdr meth-1) (null (cdr meth-2)))
		       (and (cdr meth-2) (null (cdr meth-1)))
		       (null (call-component-method-equal-internal
			       discriminator (car meth-1) (car meth-2))))
	       (return nil))))))

(defmeth call-component-method-equal-internal (discriminator meth-1 meth-2)
  (ignore discriminator meth-1 meth-2)
  t)



(defvar *method-combination-filters*
        '(:every :first :last :remove-duplicates))

(defvar *method-combination-orders*
        '(:most-specific-first :most-specific-last))

(defun make-method-combination (name parameters method-patterns body)
  (let ((causes-combination-predicate 'true)
        (combiner (make-symbol (string-append name " Method Combiner"))))
    ;; Error check and canonicalize the arguments.
    (unless (symbolp name)
      (error "The name of a method combination type must be a symbol, but ~S~
            was specified."
             name))
    ;; Check the various sub-parts of each method-patterns.  Canonicalize
    ;; each method-pattern by adding the () pattern to it if it has no
    ;; other patterns.
    (iterate ((method-patterns-loc on method-patterns))
      (destructuring-bind (var printer filter order . patterns)
                          (car method-patterns-loc)
        (check-symbol-variability var "bind (in a method-patterns)")
        (or (null (keywordp filter))
            (memq filter *method-combination-filters*)
            (error "A method-patterns filter must be one of: ~S~%not ~S."
                   *method-combination-filters* filter))
        (or (null (keywordp order))
            (memq order *method-combination-orders*)
            (error "A method-patterns order must be one of: ~S~%not ~S."
                   *method-combination-orders* filter))
        (if (null patterns)
            (setf (car method-patterns-loc)
                  (append (car method-patterns-loc) (list nil)))
            (iterate ((pattern in patterns))
              (or (listp pattern)
                  (eq pattern ':default)
                  (error "A method-pattern must be a list.~%~
                         In the method-patterns ~S, ~S is an invalid pattern."
                         (car method-patterns-loc) pattern))))))
    (iterate ()
      (while (and body (listp (car body))))
      (case (caar body)
        (:causes-combination-predicate
          (setq causes-combination-predicate (cadr (pop body))))
        (otherwise (return))))

    `(progn 
       ,(make-combiner-definer
          combiner name parameters method-patterns body)
       (setf (get ',name 'combined-method-templates) ())
       (set-method-combination-info ',name
                                    ',combiner
                                    ',causes-combination-predicate))))

(defun make-combiner-definer
       (combiner name parameters method-patterns body)
  (ignore name)
  `(defun ,combiner (.discriminator. .methods. .params.)
     .discriminator.
     (apply
       #'(lambda ,parameters
           (let ,(iterate (((var) in method-patterns)) (collect `(,var nil)))
             (do ((.method. (pop .methods.) (pop .methods.)))
                 ((null .method.))
               (cond 
                 ,@(iterate (((var nil fil ord . pats) in method-patterns))
                     (collect
		       `((and ,(ecase fil
				 (:first
				   `(if (eq ,ord :most-specific-first)
					(null ,var)
					't))
				 (:last
				   `(if (eq ,ord :most-specific-first)
					t
					(null ,var)))
				 (:every
				   't))
			      (method-matches-patterns-p .method. ',pats))
                         (push .method. ,var))))))
	     ,@(iterate (((var nil fil ord) in method-patterns))
		 (cond ((memq fil '(:first :last))
			(collect `(setq ,var (car ,var))))
		       ((eq ord ':most-specific-first)
			(collect `(setq ,var (nreverse ,var))))))
             ,@body))
       .params.)))


(defmeth method-matches-patterns-p (method patterns)
  (iterate ((pattern in patterns))
    (when (method-matches-pattern-p method pattern)
      (return t))))

(defmeth method-matches-pattern-p (method pattern)
  (iterate ((pats = pattern (cdr pats))
            (opts = (method-options method) (cdr opts)))
    (if (symbolp pats)
        ;; Special case this because it means we have to blow out of
        ;; iterate.  Should iterate should know about dotted lists.
        (return (or (eq pats '*) (eq pats opts)))
        (unless (or (eq (car pats) '*)
                    (equal (car pats) (car opts)))
          (return nil)))    
    (finally (return t))))

(defun patterns-keywords (patterns)
  (let ((keywords ()))
    (iterate ((pattern in patterns))
      (iterate ((elem in pattern))
        (when (keywordp elem) (push elem keywords))))
    keywords))

(defun check-symbol-variability (symbol verb)
  (cond ((not (symbolp symbol))
         (error "Attempt to ~A ~S which is not a symbol" verb symbol))
        ((or (null symbol) (eq symbol 't))
         (error "Attempt to ~A ~S" verb symbol))
        ((eq (symbol-package symbol) (find-package 'keyword))
         (error "Attempt to ~A ~S, which is a keyword" verb symbol))
        ((constantp symbol)
         (error "Attempt to ~A ~S, which is a constant" verb symbol))))

(defun cpl-filter-= (cpl1 cpl2 discriminator)
  (macrolet ((has-method-on-discriminator-p (class)
	       `(memq discriminator (class-direct-discriminators ,class))))
    (prog ()
       restart
          (cond ((null cpl1)
		 (if (null cpl2)
		     (return t)
		     (return nil)))
                ((null cpl2)
                 (return nil)))
          (unless (has-method-on-discriminator-p (car cpl1))
            (pop cpl1)
            (go restart))
          (unless (has-method-on-discriminator-p (car cpl2))
            (pop cpl2)
            (go restart))
          (if (neq (pop cpl1) (pop cpl2))
              (return nil)
	      (go restart)))))


;;;   class-discriminators-which-combine-methods
;;;   discriminator-methods-combine-p

(defmeth combine-methods ((class class) &optional discriminators)
  (let ((cpl (class-class-precedence-list class))
        (method nil)
        (method-cpl nil)
        (combined-method nil))
  
    (iterate ((disc in discriminators))
      (setq method (lookup-method disc class)
	    method-cpl (and method
			    (not (combined-method-p method))
			    (class-class-precedence-list
			      (car (method-type-specifiers method)))))
      (unless (cpl-filter-= cpl method-cpl disc)
	(dolist (other-method (discriminator-methods disc))
	  (when (and (combined-method-p other-method)
		     (eq (car (method-type-specifiers other-method))
			 class))
	    (remove-method disc other-method)))
	(multiple-value-bind (arguments apply-p body)
	    (combine-methods-internal class disc cpl)
	  (setq combined-method 
		(make 'combined-method
		      :function (compile-combined-method
				  disc arguments apply-p body)
		      :arglist arguments
		      :type-specifiers (cons class
					     (cdr (method-type-specifiers
						    method)))))
	  (add-method disc combined-method))))))

(defmeth combine-methods-internal (class discriminator cpl)
  (ignore class)
  (let ((methods (iterate ((c in cpl))
                   (join
		     (iterate ((m in (discriminator-methods discriminator)))
		       (when (and (eq (car (method-type-specifiers m)) c)
				  (not (combined-method-p m)))
			 (collect m)))))))
    (multiple-value-bind (required restp)
        (compute-discriminating-function-arglist-info discriminator)
      (let ((*combined-method-arguments*
              (make-discriminating-function-arglist required restp))
            (*combined-method-apply* restp))
        (values *combined-method-arguments*
                *combined-method-apply*
                (funcall (method-combiner discriminator)
                         discriminator methods ()))))))


  ;;   
;;;;;; COMPILE-COMBINED-METHOD
  ;;   

(defmeth compile-combined-method ((discriminator method-combination-mixin)
                                  *combined-method-arguments*
                                  *combined-method-apply*
                                  body)
  (multiple-value-bind (constructor methods-called)
      (compile-combined-method-internal discriminator body)
    (apply constructor (mapcar #'method-function methods-called))))

(defmeth compile-combined-method-internal (discriminator body)
  (let* ((combination-type (method-combination-type discriminator))
         (templates (get combination-type 'combined-method-templates))
         (methods-called ())
         (walked-body 
           (walk-form body
             :walk-function
             #'(lambda (form context &aux temp)
                 (ignore context)
                 (values form
                         (and (eq context 'eval)
                              (listp form)
                              (setq temp (car form))
                              (cond ((eq temp 'call-component-method)
                                     (push (cadr form) methods-called))
                                    ((eq temp 'call-component-methods)
                                     (setq methods-called
                                           (append (cadr form)
                                                   methods-called))))))))))
    (setq methods-called (remove nil methods-called))
    (iterate ((entry in templates))
      (when (combined-method-equal discriminator (car entry) walked-body)
        (return (values (cdr entry) methods-called)))
      (finally	
        (let* ((*combined-method-template*
                 (iterate ((method in methods-called))
                   (collect (cons method (gensym)))))
               (new-constructor
                 (compile ()
                          `(lambda
                             ,(mapcar #'cdr *combined-method-template*)
                             #'(lambda ,*combined-method-arguments*
                                 ,(walk-form walked-body))))))
          (push (cons walked-body new-constructor)
                (get combination-type 'combined-method-templates))
          (return (values new-constructor methods-called)))))))
  
(defmeth combined-method-equal (discriminator comb-meth-1 comb-meth-2)
  (cond ((atom comb-meth-1) (eq comb-meth-1 comb-meth-2))
        ((memq (car comb-meth-1)
               '(call-component-method call-component-methods))
         (and (eq (car comb-meth-1) (car comb-meth-2))
              (call-component-method-equal
                discriminator comb-meth-1 comb-meth-2)))
        (t
         (and (combined-method-equal
                discriminator (car comb-meth-1) (car comb-meth-2))
              (combined-method-equal
                discriminator (cdr comb-meth-1) (cdr comb-meth-2))))))



(defmeth discriminator-changed ((discriminator method-combination-mixin)
				(method combined-method)
				added-p)
  (ignore discriminator method added-p))

(defmeth discriminator-changed ((discriminator method-combination-mixin)
				method
				added-p)
  (when (methods-combine-p discriminator)
    (let ((class (car (method-type-specifiers method))))
      (when (classp class)
	(labels ((walk-tree (class)
		   (combine-methods class (list discriminator))
		   (dolist (subclass (class-direct-subclasses class))
		     (walk-tree subclass))))
	  (walk-tree class)))))
  (run-super))